package org.nahtanoj.contacts;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;

public class ContactMatch {
	private static final Logger log = Logger.getLogger(ContactMatch.class.getName());
	//TODO move these to Contact class?
	private static final String CM_HASH = "getTextHash";
	private static final String CM_NAMES = "getTextNames";
	private static final String CM_EMAIL = "getTextEmails";
	private static final String CM_PHONE = "getTextPhones";
	private static final String CM_ADDR = "getTextAddress";

	// What is typical
	// Add name to contacts just first name? no first name and email
	// just phone - but that would have either first name, email or both
	// email on it's own is likely - but that is unique
	// last name/first name which could clash would usually have email or phone which are both unique
	// so match should be on unique fields first
	// so first and last
	// if first or last are empty then match on email, then phone and then address
	// so match on hash
	// with what is left match on first and last (first+last) so no null
	// with what is left match on email, then phone
	// with what is left match on address
	// if anything left then what -
	// if hash is valid then it must have matched prior time so delete it
	// if hash is not valid then it has changed so treat as new

	// for testing we can have a hash match
	// an last and first match
	// an email match
	// a tel. match
	// an address match
	// one with a valid hash that does not match
	// one with an invalid hash and no hash that does not match




	// Match all the hashes and remove from the pool - then with what is left or if more than one identical hash
	// 1- Count the number of identical fields (convert null to empty string)
	// Pick the largest and remove from pool
	// If there is more than one
	// 2- Count the identical non null/empty string matches (exclude where both are null or empty string)
	// Pick the largest and remove from pool
	// If there is more than one
	// 3- Count the identical where neither side is null/empty string
	// Pick the largest and remove from pool
	// If there is more than one

	//	a b c
	//	a null c
	//	a b null
	//
	//	1 2,2 2 2,2 3 1,1



	//	here is

	// -so match on hash
	// -with what is left match on first and last (first+last) so no null
	// with what is left match on email, then phone
	// with what is left match on address
	// if anything left then what -
	// if hash is valid then it must have matched prior time so delete it
	// if hash is not valid then it has changed so treat as new

	//the version
	//before the wrong - matches on TextHash and other getText null

	public void match(ContactStore docs, ContactStore mail) {
		matcher(docs, mail);
	}

	private void matcher(ContactStore docs, ContactStore mail) {
		HashMap<String, HashMap<String, Contact>> matchSelf = newMatch(mail.getContacts(), docs.getContacts());
		HashMap<String, HashMap<String, Contact>> matchDocs = newMatch(docs.getContacts(), mail.getContacts());
		HashMap<String, Contact> ms = matchSelf.get("Matched");
		HashMap<String, Contact> us = matchSelf.get("Unmatch");
		HashMap<String, Contact> md = matchDocs.get("Matched");
		HashMap<String, Contact> ud = matchDocs.get("Unmatch");
		if (ms.size() != md.size()) {
			log.warning("Matching sizes differ:" + ms.size() + " " + md.size());
			System.out.println("MS:" + ms.size() + " US:" + us.size() + " MD:" + md.size() + " UD:" + ud.size());
		}
		if ((ms.size() + us.size() + md.size() + ud.size()) != (mail.getContacts().size() + docs.getContacts().size())) {
			log.warning("Contacts missing from matched and umatched lists");
		}
		for (String s : ms.keySet()) {
			Contact c = ms.get(s);
			if (c.getMatch().getMatch() != c) {
				System.out.println("Doesn't match self");
			}
		}
		for (String s : md.keySet()) {
			Contact c = md.get(s);
			if (c.getMatch().getMatch() != c) {
				System.out.println("Doesn't match self");
			}
		}
		mail.setMatches(ms, ud, us); // Matched, new docs, new mail
	}

	public HashMap<String, HashMap<String, Contact>> newMatch(HashMap<String, Contact> l1, HashMap<String, Contact> l2) {
		HashMap<String, Contact> matched = new HashMap<String, Contact>();
		HashMap<String, Contact> unmatch = new HashMap<String, Contact>();
		System.out.println("newMatch " + l1.size() + ":" + l2.size());
		for (String h1 : l1.keySet()) {
			Contact priorMatch = null;
			Contact c1 = l1.get(h1);
			System.out.println("Processing:" + c1);
			if (c1.getMatch() != null) {
				priorMatch = c1.getMatch();
				c1.setMatch(null);
			}
			// TODO what if email2matches email1, what if business matches mobile
			// TODO get rid of this multiple spaces nonsense
			//TODO can we improve this by having a group getter e.g. getName, getAddress which gets the whole name/address
			if (!matched(CM_HASH, c1, l2, matched, "")) { // Try direct hash match
				if (!matched(CM_NAMES, c1, l2, matched, " ")) { // Try and match what is left with last and first
					if (!matched(CM_EMAIL, c1, l2, matched, " ")) { // Try and match what is left with email
						if (!matched(CM_PHONE, c1, l2, matched, "   ")) { // Try and match what is left with phone
							//TODO must all of these have null in the name?
							// To be allowed to match on address name must be null or ""
							// TODO can we get rid of null and "" check by initialising to ""
							if (!matched(CM_ADDR, c1, l2, matched, "     ")) { // Try and match what is left with address
								unmatch.put(c1.hashCalc(), c1);
							}
						}
					}
				}
			}
			// Check that a previously matched contact is the same as the one matched now
			if (priorMatch != null && c1.getMatch() != null) {
				if (!priorMatch.hashCalc().equals(c1.getMatch().hashCalc())) {
					System.out.println("Not matching prior matched");
				}
			}
		}
		HashMap<String, HashMap<String, Contact>> result = new HashMap<String, HashMap<String, Contact>>();
		HashMap<String, Contact> finalMatched = new HashMap<String, Contact>();
		for (String h1 : l1.keySet()) {
			Contact c1 = l1.get(h1);
			if (c1.getMatch() != null) {
				finalMatched.put(c1.hashCalc(), c1);
			}
		}
		result.put("Matched", finalMatched);
		result.put("Unmatch", unmatch);
		return result;

	}

	private boolean matched(String method, Contact c1,
			HashMap<String, Contact> l2, HashMap<String, Contact> matched, String empty) {
		List<Contact>potentialMatch = new ArrayList<Contact>();
		for (String h2 : l2.keySet()) {
			Contact c2 = l2.get(h2);
			System.out.println("  " + method + " " + c2);
			if (method.equals(CM_ADDR) && matchAddressAllowed(c1, c2)) {
				try {
					Method m1 = c1.getClass().getMethod(method);
					Method m2 = c2.getClass().getMethod(method);
					String c1Get = (String)m1.invoke(c1);
					String c2Get = (String)m2.invoke(c2);
					if (c1Get.equals(c2Get)) {
						// TODO space comparison does nothing,
						if (!c1Get.equals(empty)) {
							potentialMatch.add(c2);
						}
					}
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
		if (potentialMatch.size() == 1) {
			c1.setMatch(potentialMatch.get(0));
			matched.put(potentialMatch.get(0).hashCalc(), potentialMatch.get(0));
			System.out.println(method + "->match " + potentialMatch.get(0));
			return true;
		}
		return false;
	}

	//	allok	1,2,4,8
	//james	fred			ok	12
	//fred	bill		ok	6
	//bill	jon	ok	3
	//james			jon	ok	9
	//james		bill		nok	10
	//fred		jon	nok	5
	//fred	bill	jon	nok	7
	//james		bill	jon	nok	11
	//james	fred		jon	nok	13
	//james	fred	bill		nok	14
	//james	fred	bill	jon	nok	15
	private boolean matchAddressAllowed(Contact c1, Contact c2) {
		boolean result = false;
		int p = 15;
		if (c1.getFirstName() == null || c1.getFirstName().length() == 0) {
			p = p - 8;
		}
		if (c1.getLastName() == null || c1.getLastName().length() == 0) {
			p = p - 4;
		}
		if (c2.getFirstName() == null || c2.getFirstName().length() == 0) {
			p = p - 2;
		}
		if (c2.getLastName() == null || c2.getLastName().length() == 0) {
			p = p - 1;
		}
		//1,2,3,4,6,8,9,12 OK
		//5,7,10,11,13,14,15 NOK
		if (p < 5 || p == 6 || p == 8 || p == 9 || p == 12) {
			result = true;
		}
		result = true;
		return result;
	}

}